﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Diagnostics;

namespace D20
{

    /// <summary>
    /// Represents a single dice roll.
    /// </summary>
    public struct DiceRoll
    {
        /// <summary>
        /// Gets the minimum outcome.
        /// </summary>
        /// <returns></returns>
        public int MinimumOutcome
        {
            get { return numberOfDie + modifier; }
        }
        /// <summary>
        /// Gets the maximum outcome.
        /// </summary>
        /// <returns></returns>
        public int MaximumOutcome
        {
            get { return numberOfDie * sidesPerDie + modifier; }
        }
        private int numberOfDie, sidesPerDie, modifier;
        /// <summary>
        /// The number of die to roll.
        /// </summary>
        /// <value>The number of die.</value>
        public int NumberOfDie
        {
            get { return numberOfDie; }
            set
            {
                Debug.Assert(value > 0, "value > 0");
                if (value <= 0)
                {
                    throw new ArgumentOutOfRangeException("value", "value must be positive.");
                }
                numberOfDie = value;
            }
        }
        /// <summary>
        /// The number of sides per die.
        /// </summary>
        /// <value>The sides per die.</value>
        public int SidesPerDie
        {
            get { return sidesPerDie; }
            set
            {
                Debug.Assert(value > 0, "value > 0");
                if (value <= 0)
                {
                    throw new ArgumentOutOfRangeException("value", "value must be positive.");
                }
                sidesPerDie = value;
            }
        }
        /// <summary>
        /// The modifier to add to the roll.
        /// </summary>
        /// <value>The modifier.</value>
        public int Modifier
        {
            get { return modifier; }
            set
            {
                modifier = value;
            }
        }
        /// <summary>
        /// Rolls the dice.
        /// </summary>
        /// <returns>The outcome of the roll.</returns>
        public int Roll()
        {
            return Dice.Roll(NumberOfDie, SidesPerDie, Modifier);
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="DiceRoll"/> struct.
        /// </summary>
        /// <param name="numberOfDie">The number of die.</param>
        /// <param name="sidesPerDie">The sides per die.</param>
        public DiceRoll(int numberOfDie, int sidesPerDie)
            : this(numberOfDie, sidesPerDie, 0)
        {

        }
        /// <summary>
        /// Initializes a new instance of the <see cref="DiceRoll"/> struct.
        /// </summary>
        /// <param name="numberOfDie">The number of die.</param>
        /// <param name="sidesPerDie">The sides per die.</param>
        /// <param name="modifier">The modifier.</param>
        public DiceRoll(int numberOfDie, int sidesPerDie, int modifier)
        {
            Debug.Assert(numberOfDie > 0, "numberOfDie > 0");
            if (numberOfDie <= 0)
            {
                throw new ArgumentOutOfRangeException("numberOfDie", "numberOfDie must be positive.");
            }

            this.numberOfDie = numberOfDie;

            Debug.Assert(sidesPerDie > 0, "sidesPerDie > 0");
            if (sidesPerDie <= 0)
            {
                throw new ArgumentOutOfRangeException("sidesPerDie", "sidesPerDie must be positive.");
            }

            this.sidesPerDie = sidesPerDie;

            this.modifier = modifier;
        }
        #region System.Object overrides

        /// <summary>
        /// Indicates whether this instance and a specified object are equal.
        /// </summary>
        /// <param name="obj">Another object to compare to.</param>
        /// <returns>
        /// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
        /// </returns>
        public override bool Equals(object obj)
        {
            if (obj is DiceRoll)
            {
                DiceRoll d = (DiceRoll)obj;

                return NumberOfDie == d.NumberOfDie && SidesPerDie == d.SidesPerDie && Modifier == d.Modifier;
            }
            return false;
        }
        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that is the hash code for this instance.
        /// </returns>
        public override int GetHashCode()
        {
            return (NumberOfDie << 15) ^ SidesPerDie ^ (Modifier << 4);
        }

        /// <summary>
        /// Returns the diceroll in standard d20 format.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> containing the description.
        /// </returns>
        public override string ToString()
        {
            string d = String.Format(CultureInfo.CurrentCulture, "{0}d{1}", NumberOfDie, SidesPerDie);
            if (Modifier > 0)
            {
                return String.Format(CultureInfo.CurrentCulture, "{0} + {1}", d, Modifier);
            }
            else if (Modifier < 0)
            {
                return String.Format(CultureInfo.CurrentCulture, "{0} - {1}", d, Math.Abs(Modifier));
            }
            else
            {
                return d;
            }
        }

        #endregion

        #region Operators

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="left">The left.</param>
        /// <param name="right">The right.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator ==(DiceRoll left, DiceRoll right)
        {
            return left.NumberOfDie == right.NumberOfDie && left.SidesPerDie == right.SidesPerDie && left.Modifier == right.Modifier;
        }


        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="left">The left.</param>
        /// <param name="right">The right.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator !=(DiceRoll left, DiceRoll right)
        {
            return !(left.NumberOfDie == right.NumberOfDie && left.SidesPerDie == right.SidesPerDie && left.Modifier == right.Modifier);
        }

        /// <summary>
        /// Adds two dice rolls together.
        /// </summary>
        /// <param name="left">The left.</param>
        /// <param name="right">The right.</param>
        /// <returns>The result of the operator.</returns>
        public static int Add(DiceRoll left, DiceRoll right)
        {
            return left.Roll() + right.Roll();
        }

        /// <summary>
        /// Adds two dice rolls together.
        /// </summary>
        /// <param name="left">The left.</param>
        /// <param name="right">The right.</param>
        /// <returns>The result of the operator.</returns>
        public static int Add(int left, DiceRoll right)
        {
            return left + right.Roll();
        }
        /// <summary>
        /// Adds two dice rolls together.
        /// </summary>
        /// <param name="left">The left.</param>
        /// <param name="right">The right.</param>
        /// <returns>The result of the operator.</returns>
        public static int Add(DiceRoll left, int right)
        {
            return left.Roll() + right;
        }

        /// <summary>
        /// Adds two dice rolls together.
        /// </summary>
        /// <param name="left">The left.</param>
        /// <param name="right">The right.</param>
        /// <returns>The result of the operator.</returns>
        public static int operator +(DiceRoll left, DiceRoll right)
        {
            return left.Roll() + right.Roll();
        }
        /// <summary>
        /// Adds two dice rolls together.
        /// </summary>
        /// <param name="left">The left.</param>
        /// <param name="right">The right.</param>
        /// <returns>The result of the operator.</returns>
        public static int operator +(int left, DiceRoll right)
        {
            return left + right.Roll();
        }
        /// <summary>
        /// Adds two dice rolls together.
        /// </summary>
        /// <param name="left">The left.</param>
        /// <param name="right">The right.</param>
        /// <returns>The result of the operator.</returns>
        public static int operator +(DiceRoll left, int right)
        {
            return left.Roll() + right;
        }
        #endregion
    }


    /// <summary>
    /// A class for representing dice.
    /// </summary>
    public static class Dice
    {
        private static Random random = new Random();
        /// <summary>
        /// Rolls the specified number of die.
        /// </summary>
        /// <example>
        /// To roll 2d20 +3, you would call the following:
        /// 
        /// Dice.Roll(2, 20, 3);
        /// </example>
        /// <param name="numberOfDie">The number of die.</param>
        /// <param name="sidesPerDie">The die sides.</param>
        /// <param name="modifier">The modifier.</param>
        /// <returns></returns>
        public static int Roll(int numberOfDie, int sidesPerDie, int modifier)
        {
            Debug.Assert(numberOfDie > 0, "numberOfDie > 0");
            if (numberOfDie <= 0)
            {
                throw new ArgumentOutOfRangeException("numberOfDie", "numberOfDie must be positive.");
            }

            Debug.Assert(sidesPerDie > 0, "sidesPerDie > 0");
            if (sidesPerDie <= 0)
            {
                throw new ArgumentOutOfRangeException("sidesPerDie", "sidesPerDie must be positive.");
            }

            int min = numberOfDie;
            int max = numberOfDie * sidesPerDie;
            return random.Next(min, max) + modifier;
        }
    }
}
